/*-------------------------------------------------------------------------
 *
 * gininsert.c
 *	  postgres 倒排索引访问方法的插入例程。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/gininsert.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/ginxlog.h"
#include "access/tableam.h"
#include "access/xloginsert.h"
#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/indexfsm.h"
#include "storage/predicate.h"
#include "storage/smgr.h"
#include "utils/memutils.h"
#include "utils/rel.h"

typedef struct
{
	GinState	ginstate;
	double		indtuples;
	GinStatsData buildStats;
	MemoryContext tmpCtx;
	MemoryContext funcCtx;
	BuildAccumulator accum;
} GinBuildState;


/*
 * 将项目指针数组添加到元组的发布列表中，或者
 * 在空间不足的情况下创建发布树和指向树的元组。
 * 元组的最大大小在 GinFormTuple() 中定义。
 * 返回一个新的、修改后的索引元组。
 * items[] 必须按顺序排列且不重复。
 */
static IndexTuple fc_addItemPointersToLeafTuple(GinState *fc_ginstate,
						   IndexTuple fc_old,
						   ItemPointerData *fc_items, uint32 fc_nitem,
						   GinStatsData *fc_buildStats, Buffer fc_buffer)
{
	OffsetNumber fc_attnum;
	Datum		fc_key;
	GinNullCategory fc_category;
	IndexTuple	fc_res;
	ItemPointerData *fc_newItems,
			   *fc_oldItems;
	int			fc_oldNPosting,
				fc_newNPosting;
	GinPostingList *fc_compressedList;

	Assert(!GinIsPostingTree(fc_old));

	fc_attnum = gintuple_get_attrnum(fc_ginstate, fc_old);
	fc_key = gintuple_get_key(fc_ginstate, fc_old, &fc_category);

	/* 合并旧的和新的发布列表 */
	fc_oldItems = ginReadTuple(fc_ginstate, fc_attnum, fc_old, &fc_oldNPosting);

	fc_newItems = ginMergeItemPointers(fc_items, fc_nitem,
									fc_oldItems, fc_oldNPosting,
									&fc_newNPosting);

	/* 压缩发布列表，并尝试构建一个有足够空间的元组 */
	fc_res = NULL;
	fc_compressedList = ginCompressPostingList(fc_newItems, fc_newNPosting, GinMaxItemSize,
											NULL);
	pfree(fc_newItems);
	if (fc_compressedList)
	{
		fc_res = GinFormTuple(fc_ginstate, fc_attnum, fc_key, fc_category,
						   (char *) fc_compressedList,
						   SizeOfGinPostingList(fc_compressedList),
						   fc_newNPosting,
						   false);
		pfree(fc_compressedList);
	}
	if (!fc_res)
	{
		/* 发布列表将太大，转换为发布树 */
		BlockNumber fc_postingRoot;

		/*
		 * 用旧元组的发布列表初始化发布树。它
		 * 肯定足够小，可以放在一个发布树页面上，并且应该
		 * 已经是有序且没有重复的。
		 */
		fc_postingRoot = createPostingTree(fc_ginstate->index,
										fc_oldItems,
										fc_oldNPosting,
										fc_buildStats,
										fc_buffer);

		/* 现在将要添加的 TIDs 插入发布树 */
		ginInsertItemPointers(fc_ginstate->index, fc_postingRoot,
							  fc_items, fc_nitem,
							  fc_buildStats);

		/* 并构建一个新的仅发布树结果元组 */
		fc_res = GinFormTuple(fc_ginstate, fc_attnum, fc_key, fc_category, NULL, 0, 0, true);
		GinSetPostingTree(fc_res, fc_postingRoot);
	}
	pfree(fc_oldItems);

	return fc_res;
}

/*
 * 构建一个新的叶子元组，格式可以是发布列表或发布树
 * 取决于给定的项目列表是否能够适应。
 * items[] 必须按顺序排列且不重复。
 *
 * 这基本上与 addItemPointersToLeafTuple 中的逻辑相同，
 * 但从稍微不同的输入进行工作。
 */
static IndexTuple fc_buildFreshLeafTuple(GinState *fc_ginstate,
					OffsetNumber fc_attnum, Datum fc_key, GinNullCategory fc_category,
					ItemPointerData *fc_items, uint32 fc_nitem,
					GinStatsData *fc_buildStats, Buffer fc_buffer)
{
	IndexTuple	fc_res = NULL;
	GinPostingList *fc_compressedList;

	/* 尝试使用所有项目构建一个发布列表元组 */
	fc_compressedList = ginCompressPostingList(fc_items, fc_nitem, GinMaxItemSize, NULL);
	if (fc_compressedList)
	{
		fc_res = GinFormTuple(fc_ginstate, fc_attnum, fc_key, fc_category,
						   (char *) fc_compressedList,
						   SizeOfGinPostingList(fc_compressedList),
						   fc_nitem, false);
		pfree(fc_compressedList);
	}
	if (!fc_res)
	{
		/* 发布列表将太大，构建发布树 */
		BlockNumber fc_postingRoot;

		/*
		 * 构建仅发布树的结果元组。我们首先这样做，以便
		 * 如果键太大可以快速失败。
		 */
		fc_res = GinFormTuple(fc_ginstate, fc_attnum, fc_key, fc_category, NULL, 0, 0, true);

		/*
		 * 使用 TIDs 初始化一个新的发布树。
		 */
		fc_postingRoot = createPostingTree(fc_ginstate->index, fc_items, fc_nitem,
										fc_buildStats, fc_buffer);

		/* 并在结果元组中保存根链接 */
		GinSetPostingTree(fc_res, fc_postingRoot);
	}

	return fc_res;
}

/*
 * 插入一个或多个与给定键值相关的堆 TIDs。
 * 这将插入一个单独的键条目，或扩大一个现有条目。
 *
 * 在索引构建过程中，buildStats 非空，且其包含的计数器
 * 应根据需要递增。
 */
void ginEntryInsert(GinState *fc_ginstate,
			   OffsetNumber fc_attnum, Datum fc_key, GinNullCategory fc_category,
			   ItemPointerData *fc_items, uint32 fc_nitem,
			   GinStatsData *fc_buildStats)
{
	GinBtreeData fc_btree;
	GinBtreeEntryInsertData fc_insertdata;
	GinBtreeStack *fc_stack;
	IndexTuple	fc_itup;
	Page		fc_page;

	fc_insertdata.isDelete = false;

	ginPrepareEntryScan(&fc_btree, fc_attnum, fc_key, fc_category, fc_ginstate);
	fc_btree.isBuild = (fc_buildStats != NULL);

	fc_stack = ginFindLeafPage(&fc_btree, false, false, NULL);
	fc_page = BufferGetPage(fc_stack->buffer);

	if (fc_btree.findItem(&fc_btree, fc_stack))
	{
		/* 找到现有条目 */
		fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_stack->off));

		if (GinIsPostingTree(fc_itup))
		{
			/* 向现有发布树添加条目 */
			BlockNumber fc_rootPostingTree = GinGetPostingTree(fc_itup);

			/* 释放所有堆栈 */
			LockBuffer(fc_stack->buffer, GIN_UNLOCK);
			freeGinBtreeStack(fc_stack);

			/* 插入发布树 */
			ginInsertItemPointers(fc_ginstate->index, fc_rootPostingTree,
								  fc_items, fc_nitem,
								  fc_buildStats);
			return;
		}

		CheckForSerializableConflictIn(fc_ginstate->index, NULL,
									   BufferGetBlockNumber(fc_stack->buffer));
		/* 修改现有叶子条目 */
		fc_itup = fc_addItemPointersToLeafTuple(fc_ginstate, fc_itup,
										  fc_items, fc_nitem, fc_buildStats, fc_stack->buffer);

		fc_insertdata.isDelete = true;
	}
	else
	{
		CheckForSerializableConflictIn(fc_ginstate->index, NULL,
									   BufferGetBlockNumber(fc_stack->buffer));
		/* 没有匹配，因此构造一个新的叶子条目 */
		fc_itup = fc_buildFreshLeafTuple(fc_ginstate, fc_attnum, fc_key, fc_category,
								   fc_items, fc_nitem, fc_buildStats, fc_stack->buffer);

		/*
		 * nEntries 计算叶子元组，因此仅在我们创建一个
		 * 新元组时递增它。
		 */
		if (fc_buildStats)
			fc_buildStats->nEntries++;
	}

	/* 插入新的或修改过的叶子元组 */
	fc_insertdata.entry = fc_itup;
	ginInsertValue(&fc_btree, fc_stack, &fc_insertdata, fc_buildStats);
	pfree(fc_itup);
}

/*
 * 为单个可索引项目提取索引条目，并将其添加到
 * BuildAccumulator 的状态中。
 *
 * 该函数仅在初始索引创建期间使用。
 */
static void fc_ginHeapTupleBulkInsert(GinBuildState *fc_buildstate, OffsetNumber fc_attnum,
					   Datum fc_value, bool fc_isNull,
					   ItemPointer fc_heapptr)
{
	Datum	   *fc_entries;
	GinNullCategory *fc_categories;
	int32		fc_nentries;
	MemoryContext fc_oldCtx;

	fc_oldCtx = MemoryContextSwitchTo(fc_buildstate->funcCtx);
	fc_entries = ginExtractEntries(fc_buildstate->accum.ginstate, fc_attnum,
								fc_value, fc_isNull,
								&fc_nentries, &fc_categories);
	MemoryContextSwitchTo(fc_oldCtx);

	ginInsertBAEntries(&fc_buildstate->accum, fc_heapptr, fc_attnum,
					   fc_entries, fc_categories, fc_nentries);

	fc_buildstate->indtuples += fc_nentries;

	MemoryContextReset(fc_buildstate->funcCtx);
}

static void fc_ginBuildCallback(Relation fc_index, ItemPointer fc_tid, Datum *fc_values,
				 bool *fc_isnull, bool fc_tupleIsAlive, void *fc_state)
{
	GinBuildState *fc_buildstate = (GinBuildState *) fc_state;
	MemoryContext fc_oldCtx;
	int			fc_i;

	fc_oldCtx = MemoryContextSwitchTo(fc_buildstate->tmpCtx);

	for (fc_i = 0; fc_i < fc_buildstate->ginstate.origTupdesc->natts; fc_i++)
		fc_ginHeapTupleBulkInsert(fc_buildstate, (OffsetNumber) (fc_i + 1),
							   fc_values[fc_i], fc_isnull[fc_i], fc_tid);

	/* 如果我们最大化了可用内存，则将所有内容转储到索引中 */
	if (fc_buildstate->accum.allocatedMemory >= (Size) maintenance_work_mem * 1024L)
	{
		ItemPointerData *fc_list;
		Datum		fc_key;
		GinNullCategory fc_category;
		uint32		fc_nlist;
		OffsetNumber fc_attnum;

		ginBeginBAScan(&fc_buildstate->accum);
		while ((fc_list = ginGetBAEntry(&fc_buildstate->accum,
									 &fc_attnum, &fc_key, &fc_category, &fc_nlist)) != NULL)
		{
			/* 可能会有许多条目，因此在这里愿意中止 */
			CHECK_FOR_INTERRUPTS();
			ginEntryInsert(&fc_buildstate->ginstate, fc_attnum, fc_key, fc_category,
						   fc_list, fc_nlist, &fc_buildstate->buildStats);
		}

		MemoryContextReset(fc_buildstate->tmpCtx);
		ginInitBA(&fc_buildstate->accum);
	}

	MemoryContextSwitchTo(fc_oldCtx);
}

IndexBuildResult * ginbuild(Relation fc_heap, Relation fc_index, IndexInfo *fc_indexInfo)
{
	IndexBuildResult *fc_result;
	double		fc_reltuples;
	GinBuildState fc_buildstate;
	Buffer		fc_RootBuffer,
				fc_MetaBuffer;
	ItemPointerData *fc_list;
	Datum		fc_key;
	GinNullCategory fc_category;
	uint32		fc_nlist;
	MemoryContext fc_oldCtx;
	OffsetNumber fc_attnum;

	if (RelationGetNumberOfBlocks(fc_index) != 0)
		elog(ERROR, "index \"%s\" already contains data",
			 RelationGetRelationName(fc_index));

	initGinState(&fc_buildstate.ginstate, fc_index);
	fc_buildstate.indtuples = 0;
	memset(&fc_buildstate.buildStats, 0, sizeof(GinStatsData));

	/* 初始化元数据页面 */
	fc_MetaBuffer = GinNewBuffer(fc_index);

	/* 初始化根页面 */
	fc_RootBuffer = GinNewBuffer(fc_index);

	START_CRIT_SECTION();
	GinInitMetabuffer(fc_MetaBuffer);
	MarkBufferDirty(fc_MetaBuffer);
	GinInitBuffer(fc_RootBuffer, GIN_LEAF);
	MarkBufferDirty(fc_RootBuffer);


	UnlockReleaseBuffer(fc_MetaBuffer);
	UnlockReleaseBuffer(fc_RootBuffer);
	END_CRIT_SECTION();

	/* 将根计为第一个条目页面 */
	fc_buildstate.buildStats.nEntryPages++;

	/*
	 * 创建一个临时内存上下文，用于保存尚未
	 * 转储到索引中的数据
	 */
	fc_buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
											  "Gin build temporary context",
											  ALLOCSET_DEFAULT_SIZES);

	/*
	 * 创建一个临时内存上下文，用于调用
	 * ginExtractEntries()，并可以在每个元组后重置
	 */
	fc_buildstate.funcCtx = AllocSetContextCreate(CurrentMemoryContext,
											   "Gin build temporary context for user-defined function",
											   ALLOCSET_DEFAULT_SIZES);

	fc_buildstate.accum.ginstate = &fc_buildstate.ginstate;
	ginInitBA(&fc_buildstate.accum);

	/*
	 * 执行堆扫描。我们在这里不允许同步扫描，因为 dataPlaceToPage
	 * 更倾向于接收按 TID 顺序的元组。
	 */
	fc_reltuples = table_index_build_scan(fc_heap, fc_index, fc_indexInfo, false, true,
									   fc_ginBuildCallback, (void *) &fc_buildstate,
									   NULL);

	/* 转储剩余条目到索引 */
	fc_oldCtx = MemoryContextSwitchTo(fc_buildstate.tmpCtx);
	ginBeginBAScan(&fc_buildstate.accum);
	while ((fc_list = ginGetBAEntry(&fc_buildstate.accum,
								 &fc_attnum, &fc_key, &fc_category, &fc_nlist)) != NULL)
	{
		/* 可能会有许多条目，因此在这里愿意中止 */
		CHECK_FOR_INTERRUPTS();
		ginEntryInsert(&fc_buildstate.ginstate, fc_attnum, fc_key, fc_category,
					   fc_list, fc_nlist, &fc_buildstate.buildStats);
	}
	MemoryContextSwitchTo(fc_oldCtx);

	MemoryContextDelete(fc_buildstate.funcCtx);
	MemoryContextDelete(fc_buildstate.tmpCtx);

	/*
	 * 更新元页面统计信息
	 */
	fc_buildstate.buildStats.nTotalPages = RelationGetNumberOfBlocks(fc_index);
	ginUpdateStats(fc_index, &fc_buildstate.buildStats, true);

	/*
	 * 在构建索引时我们没有写WAL记录，因此如果需要WAL日志记录，
	 * 现在将所有页面写入WAL。
	 */
	if (RelationNeedsWAL(fc_index))
	{
		log_newpage_range(fc_index, MAIN_FORKNUM,
						  0, RelationGetNumberOfBlocks(fc_index),
						  true);
	}

	/*
	 * 返回统计信息
	 */
	fc_result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));

	fc_result->heap_tuples = fc_reltuples;
	fc_result->index_tuples = fc_buildstate.indtuples;

	return fc_result;
}

/*
 *	ginbuildempty() -- 在初始化分支中构建一个空的gin索引
 */
void ginbuildempty(Relation fc_index)
{
	Buffer		fc_RootBuffer,
				fc_MetaBuffer;

	/* 一个空的GIN索引有两个页面。 */
	fc_MetaBuffer =
		ReadBufferExtended(fc_index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
	LockBuffer(fc_MetaBuffer, BUFFER_LOCK_EXCLUSIVE);
	fc_RootBuffer =
		ReadBufferExtended(fc_index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
	LockBuffer(fc_RootBuffer, BUFFER_LOCK_EXCLUSIVE);

	/* 初始化并记录元缓冲区和根缓冲区。 */
	START_CRIT_SECTION();
	GinInitMetabuffer(fc_MetaBuffer);
	MarkBufferDirty(fc_MetaBuffer);
	log_newpage_buffer(fc_MetaBuffer, true);
	GinInitBuffer(fc_RootBuffer, GIN_LEAF);
	MarkBufferDirty(fc_RootBuffer);
	log_newpage_buffer(fc_RootBuffer, false);
	END_CRIT_SECTION();

	/* 解锁并释放缓冲区。 */
	UnlockReleaseBuffer(fc_MetaBuffer);
	UnlockReleaseBuffer(fc_RootBuffer);
}

/*
 * 在“正常” （非快速更新）插入期间为单个可索引项插入索引条目
 */
static void fc_ginHeapTupleInsert(GinState *fc_ginstate, OffsetNumber fc_attnum,
				   Datum fc_value, bool fc_isNull,
				   ItemPointer fc_item)
{
	Datum	   *fc_entries;
	GinNullCategory *fc_categories;
	int32		fc_i,
				fc_nentries;

	fc_entries = ginExtractEntries(fc_ginstate, fc_attnum, fc_value, fc_isNull,
								&fc_nentries, &fc_categories);

	for (fc_i = 0; fc_i < fc_nentries; fc_i++)
		ginEntryInsert(fc_ginstate, fc_attnum, fc_entries[fc_i], fc_categories[fc_i],
					   fc_item, 1, NULL);
}

bool gininsert(Relation fc_index, Datum *fc_values, bool *fc_isnull,
		  ItemPointer fc_ht_ctid, Relation fc_heapRel,
		  IndexUniqueCheck fc_checkUnique,
		  bool fc_indexUnchanged,
		  IndexInfo *fc_indexInfo)
{
	GinState   *fc_ginstate = (GinState *) fc_indexInfo->ii_AmCache;
	MemoryContext fc_oldCtx;
	MemoryContext fc_insertCtx;
	int			fc_i;

	/* 如果在此语句中的第一次调用，则初始化GinState缓存 */
	if (fc_ginstate == NULL)
	{
		fc_oldCtx = MemoryContextSwitchTo(fc_indexInfo->ii_Context);
		fc_ginstate = (GinState *) palloc(sizeof(GinState));
		initGinState(fc_ginstate, fc_index);
		fc_indexInfo->ii_AmCache = (void *) fc_ginstate;
		MemoryContextSwitchTo(fc_oldCtx);
	}

	fc_insertCtx = AllocSetContextCreate(CurrentMemoryContext,
									  "Gin insert temporary context",
									  ALLOCSET_DEFAULT_SIZES);

	fc_oldCtx = MemoryContextSwitchTo(fc_insertCtx);

	if (GinGetUseFastUpdate(fc_index))
	{
		GinTupleCollector fc_collector;

		memset(&fc_collector, 0, sizeof(GinTupleCollector));

		for (fc_i = 0; fc_i < fc_ginstate->origTupdesc->natts; fc_i++)
			ginHeapTupleFastCollect(fc_ginstate, &fc_collector,
									(OffsetNumber) (fc_i + 1),
									fc_values[fc_i], fc_isnull[fc_i],
									fc_ht_ctid);

		ginHeapTupleFastInsert(fc_ginstate, &fc_collector);
	}
	else
	{
		for (fc_i = 0; fc_i < fc_ginstate->origTupdesc->natts; fc_i++)
			fc_ginHeapTupleInsert(fc_ginstate, (OffsetNumber) (fc_i + 1),
							   fc_values[fc_i], fc_isnull[fc_i],
							   fc_ht_ctid);
	}

	MemoryContextSwitchTo(fc_oldCtx);
	MemoryContextDelete(fc_insertCtx);

	return false;
}
